package weeny.pre.face;

import java.awt.event.ActionEvent;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import prefuse.data.Table;
import prefuse.data.event.EventConstants;
import prefuse.data.event.TableListener;
import prefuse.data.expression.BooleanLiteral;
import prefuse.data.expression.NotPredicate;
import prefuse.data.expression.Predicate;
import prefuse.util.ColorLib;
import prefuse.util.PrefuseLib;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
import weeny.pre.display.MoreVisualization;
import weeny.pre.event.GroupListener;
import weeny.pre.expression.SafePredicate;
import weeny.util.logs.Debug;

/**
 * A special case of {@link ExpressionBox}, which targets at filtering visual
 * items. You can use {@link #getPred()} to get the parsed {@link Predicate}.
 * 
 * @author weeny
 *
 */
public class FilterBox extends ExpressionBox{
	
	
	public FilterBox(MoreVisualization vis) {
		this(vis, "Filter:");
	}
	public FilterBox(MoreVisualization vis, String label){
		super(vis, label);
		setText("TRUE");
		updateActiveExpr();
		this.resetFuncList(BOOL_ITEMS);
		oldColor.put(VisualItem.FILLCOLOR, new HashMap());
		oldColor.put(VisualItem.STROKECOLOR, new HashMap());
		oldColor.put(VisualItem.TEXTCOLOR, new HashMap());		
		this.enableHighlight(true);		
	}
	
	public Predicate getPred(){
		if (getExpr() instanceof Predicate) {
			return new SafePredicate(getExpr());
		}
		else {
			Debug.prob("Not a Predicate, use \"FALSE\" instead!!");
			return BooleanLiteral.FALSE; 
		}
	}
	private boolean enableHighlight = false;
	private int dimAlpha = 20;
	private Map<String, Map<VisualItem, Integer>> oldColor = new HashMap();
	private boolean enableTableListener = false;
	
	public void setDimAlpha(int alpha){
		this.dimAlpha = alpha;
	}
	
	/**
	 * If this flag is on, when you hit the "enter" key, all the non-filtered
	 * item will dim out to highlight the filtered ones. 
	 * 
	 * But when you done processing the filtered ones, PLEASE type "true" to
	 * cancel all the highlight hints.
	 *
	 * We remember all the old color value when you hit the "enter" key, but we
	 * do not monitor the colors afterwards. So if you change the dimmed items'
	 * color, those colors will be override when you type "true"! In other words
	 * you should only edit the filtered items after you filter the items and
	 * before you restore them back. (OR, you can write tablelistener and
	 * grouplistener to track all the group operations and table operations, but
	 * that is really work to do.)
	 */
	public void enableHighlight(boolean flag){
		this.enableHighlight = flag;
		if (!flag) {
			restoreAllColors();			
		}
		else {
			dimContextColors();
		}
		vis.repaint();
	}
	public boolean enableHighlight(){
		return enableHighlight;
	}
	public void enableTableListener(boolean flag){
		this.enableTableListener  = flag;
	}
	
	private void restoreAllColors(){
		for (String field : oldColor.keySet()){
			Map<VisualItem, Integer> fieldColorMap = oldColor.get(field);
			for(VisualItem vi : fieldColorMap.keySet()){
				try {
					PrefuseLib.updateInt(vi, field, fieldColorMap.get(vi));	
				} catch (Exception e2) {
					if (!vi.getTable().isValidRow(vi.getRow())){
						Debug.prob("Item deleted? (r="+vi.getRow()+")"+vi);
					}
					else{
						Debug.prob("Unknown error: (r="+vi.getRow()+")"+vi);
					}
				}
			}
			fieldColorMap.clear();
		}
	}
	
	private void dimContextColors(){
		if (getPred() != null && !(getPred() instanceof BooleanLiteral)){
			Predicate dimP = new NotPredicate(getPred());
			for(Iterator<VisualItem> it = vis.items(dimP); it.hasNext();){
				VisualItem vi = it.next();
				for(String field : oldColor.keySet()) {
					int old = vi.getInt(field);
					int newCol = ColorLib.setAlpha(old, dimAlpha);
					PrefuseLib.updateInt(vi, field, newCol);
					oldColor.get(field).put(vi, old);
				}
			}
			if (enableTableListener) {
				addRemoveTableListener(true);	
			}
		}
	}
	
	@Override
	protected void fireActions(ActionEvent e) {
		if (enableHighlight) {
			if (enableTableListener) {
				addRemoveTableListener(false);	
			}
			restoreAllColors();
			dimContextColors();
			
			vis.repaint();
		}
		super.fireActions(e);
	}
	
	private void addRemoveTableListener(boolean add){
		Set<String> groups = vis.getGroups();
		for (String string : groups) {
			if (vis.getGroup(string) instanceof Table) {
				if (add) {
					((Table)vis.getGroup(string)).addTableListener(colortl);
				}
				else{
					((Table)vis.getGroup(string)).removeTableListener(colortl);
				}
			}
		}
		if (add) {
			vis.addGroupListener(tableGroupListener);
		}
		else {
			vis.removeGroupListener(tableGroupListener);
		}
	}
	private TableListener colortl = new TableListener() {
		public void tableChanged(Table t, int s, int e, int col, int type) {
			if (!(t instanceof VisualTable)) {
				throw new IllegalArgumentException("Should be a VisualTable");
			}
			VisualTable vt = (VisualTable) t;
			if (type == EventConstants.UPDATE) {
				if (col == EventConstants.ALL_COLUMNS) {
					return;
				}
				String colName = vt.getColumnName(col);
				if (oldColor.containsKey(colName)) {
					//you are editing the color field, so we need to check if
					//you are editing a dimmed visual item. If you are, we need
					//to update its color in the oldColor map
					Map<VisualItem, Integer> map = oldColor.get(colName);
					for(int i = s; i<=e; i++){
						VisualItem vi = vt.getItem(i);
						if (map.containsKey(vi)) {
							//bingo! we need to update its color
							//cancel the listener to prevent the chain reaction
							vi.getTable().removeTableListener(colortl);
							int oldCol = vi.getInt(colName);
							map.put(vi, oldCol);
							int newCol = ColorLib.setAlpha(oldCol, dimAlpha);
							PrefuseLib.updateInt(vi, colName, newCol);
							//add the listener back
							vi.getTable().addTableListener(colortl);
						}
					}
				}
			}
			if (type == EventConstants.INSERT){
				if (col == EventConstants.ALL_COLUMNS) {
					
				}
			}
		}
	};
	private GroupListener tableGroupListener = new GroupListener() {
		public void groupUpdated(String[] added, String[] removed) {
			for (int i = 0; added!=null && i<added.length; i++){
				if (vis.getGroup(added[i]) instanceof Table){
					((Table)vis.getGroup(added[i])).addTableListener(colortl);
				}
			}
			for (int i = 0; removed!=null && i<removed.length; i++){
				if (vis.getGroup(removed[i]) instanceof Table){
					((Table)vis.getGroup(removed[i])).addTableListener(colortl);
				}
			}
		}
	};
}